module ps2_keyboard(clk,resetn,ps2_clk,ps2_data);
    input clk,resetn,ps2_clk,ps2_data;

    reg [9:0] buffer;        // ps2_data bits
    reg [3:0] count;  // count ps2_data bits
    reg [2:0] ps2_clk_sync;

    always @(posedge clk) begin
        ps2_clk_sync <=  {ps2_clk_sync[1:0],ps2_clk};
    end

    wire sampling = ps2_clk_sync[2] & ~ps2_clk_sync[1];

    always @(posedge clk) begin
        if (resetn == 0) begin // reset
            count <= 0;
        end
        else begin
            if (sampling) begin
              if (count == 4'd10) begin
                if ((buffer[0] == 0) &&  // start bit
                    (ps2_data)       &&  // stop bit
                    (^buffer[9:1])) begin      // odd  parity
                    $display("receive %x", buffer[8:1]);
                end
                count <= 0;     // for next
              end else begin
                buffer[count] <= ps2_data;  // store ps2_data
                count <= count + 3'b1;
              end
            end
        end
    end

endmodule

module ps2_keyboard_fifo(clk,clrn,ps2_clk,ps2_data,data,
                    ready,nextdata_n,overflow);
    input clk,clrn,ps2_clk,ps2_data;
    input nextdata_n;
    output [7:0] data;
    output reg ready;
    output reg overflow;     // fifo overflow
    // internal signal, for test
    reg [9:0] buffer;        // ps2_data bits
    reg [7:0] fifo[7:0];     // data fifo
    reg [2:0] w_ptr,r_ptr;   // fifo write and read pointers
    reg [3:0] count;  // count ps2_data bits
    // detect falling edge of ps2_clk
    reg [2:0] ps2_clk_sync;

    always @(posedge clk) begin
        ps2_clk_sync <=  {ps2_clk_sync[1:0],ps2_clk};
    end

    wire sampling = ps2_clk_sync[2] & ~ps2_clk_sync[1];

    always @(posedge clk) begin
        if (clrn == 0) begin // reset
            count <= 0; w_ptr <= 0; r_ptr <= 0; overflow <= 0; ready<= 0;
        end
        else begin
            if ( ready ) begin // read to output next data
                if(nextdata_n == 1'b0) //read next data
                begin
                    r_ptr <= r_ptr + 3'b1;
                    if(w_ptr==(r_ptr+1'b1)) //empty
                        ready <= 1'b0;
                end
            end
            if (sampling) begin
              if (count == 4'd10) begin
                if ((buffer[0] == 0) &&  // start bit
                    (ps2_data)       &&  // stop bit
                    (^buffer[9:1])) begin      // odd  parity
                    fifo[w_ptr] <= buffer[8:1];  // kbd scan code
                    w_ptr <= w_ptr+3'b1;
                    ready <= 1'b1;
                    overflow <= overflow | (r_ptr == (w_ptr + 3'b1));
                    $display("receive %x", buffer[8:1]);
                end
                count <= 0;     // for next
              end else begin
                buffer[count] <= ps2_data;  // store ps2_data
                count <= count + 3'b1;
              end
            end
        end
    end
    assign data = fifo[r_ptr]; //always set output data

endmodule

// 键盘处理系统
module ps2_keyboard_handle(clk, clrn, data, ready, overflow, nextdata_n, fifo, wptr, up, state, next_state, count, CapsLock);
  input clk, clrn;
  input [7:0] data;
  input ready;
  input overflow;
  output nextdata_n;
  output reg[7:0] fifo[3:0];
  output reg [1:0] wptr;
  output up;
  output reg[1:0] state, next_state;
  output reg[7:0] count;
  output reg CapsLock;
  // 断码有两个字节
  parameter WAIT=2'd0, ON=2'd1, OFF_1=2'd2, OFF_2=2'd3;
  wire off, capslock, shift, ctrl;
  assign off = data==8'hF0;
  assign capslock = fifo[wptr-1'b1]==8'h58;
  always @(*) begin
      case(state)
          ON: next_state = (ready & off)? OFF_1: ON;
          OFF_1: next_state = ready? OFF_2:OFF_1;
          OFF_2: next_state = ready? ON:WAIT;
          WAIT: next_state = ready? ON: WAIT;
      endcase
  end
  assign up = state!=ON;//非接通状态就是按键松开
  assign nextdata_n = ~ready;
  always @(posedge clk) begin
    if (clrn==0) begin
	  wptr <= 2'b0;
          state <= WAIT;
          CapsLock <= 1'b0;
    end else begin
      state <= next_state;
      count <= (state==OFF_2)? (count + 1'b1): count;//状态机处于OFF_1可能很久，因为ready的问题，而OFF_2只可能出现一个周期，所以应该用OFF_2
      CapsLock <= (state==OFF_2 & capslock)? (~CapsLock): CapsLock;
      if (ready) begin
        fifo[wptr] <= data;
        wptr <= wptr + 2'b1;
      end
    end
  end
endmodule
